﻿Imports System.Net
Imports System.Net.Sockets

Public Class Manager
    Private Sub New()
    End Sub

    Private Shared mListener As Listener
    Private Shared mReconnectThread As System.Threading.Thread

    Shared Sub New()
        Framework.Settings.Provider.RegisterTab(Settings.MyInfoSettingsTabKey, GetType(MyInfoSettingsControl))
        Framework.Settings.Provider.RegisterTab(Settings.NetworkSettingsTabKey, GetType(NetworkSettingsControl))
    End Sub

    Public Shared Sub Start()
        RegisterLocalCommands()
        If Settings.Listen AndAlso mListener Is Nothing Then
            mListener = New Listener
            mListener.Start()
        End If
        If Settings.AutoConnect Then StartAutoConnect()
        ThrottledNetworkStream.Start()
    End Sub

    Private Shared Sub StartAutoConnect()
        If mReconnectThread Is Nothing Then
            mReconnectThread = New System.Threading.Thread(AddressOf ReconnectWorker)
            mReconnectThread.Priority = System.Threading.ThreadPriority.BelowNormal
            mReconnectThread.Name = "Reconnect Worker Thread"
            mReconnectThread.IsBackground = True
            mReconnectThread.Start()
        End If
    End Sub
    Private Shared Sub StopAutoConnect(ByVal message As Framework.Message)
        If mReconnectThread IsNot Nothing Then
            message.SetMessage("Shutting down autoconnect...")
            mReconnectThread.Abort()
            mReconnectThread.Join(10000)
            mReconnectThread = Nothing
        End If
    End Sub

    Private Shared Sub RegisterLocalCommands()
        Static mBeenDone As Boolean
        If Not mBeenDone Then
            RegisterCommand(New Commands.BuddyInformationUpdated)
            mBeenDone = True
        End If
    End Sub

    Public Shared Sub [Stop](ByVal message As Framework.Message)
        StopAutoConnect(message)

        If mListener IsNot Nothing Then
            mListener.Stop(message)
            mListener = Nothing
        End If
        For Each clsNode As Node In GetAllNodes()
            clsNode.Disconnect()
        Next
        Dim dtGiveUpAt As Date = Now.AddSeconds(10).ToUniversalTime
        While mNodes.Count > 0 And Now.ToUniversalTime < dtGiveUpAt
            System.Windows.Forms.Application.DoEvents()
            System.Threading.Thread.Sleep(100)
        End While
        For Each clsNode As Node In GetAllNodes()
            clsNode.CloseAndAbort()
        Next
        SyncLock mNodes
            mNodes.Clear()
        End SyncLock
        ThrottledNetworkStream.Stop()
    End Sub

    Private Shared Sub ReconnectWorker()
        Do
            Try
                'wait 10 seconds until first connect attempt
                System.Threading.Thread.Sleep(5000)
                For Each clsBuddy As Entities.Buddy In Framework.Buddies.Manager.GetAll
                    Select Case GetBuddyStatus(clsBuddy)
                        Case BuddyStatus.Offline
                            If Not String.IsNullOrEmpty(clsBuddy.Host) Then
                                StartConnect(clsBuddy, True)
                            End If
                    End Select
                Next
                System.Threading.Thread.Sleep(10000)
            Catch ex As System.Threading.ThreadAbortException
                Exit Do
            Catch ex As Exception
            End Try
        Loop
    End Sub

    Public Shared Sub ReevalSettings()
        If Not Settings.Listen Then
            Using clsMessage As New Framework.Message()
                [Stop](clsMessage)
            End Using
        ElseIf mListener Is Nothing Then
            Start()
        ElseIf mListener.CurrentPort <> Settings.Port Then
            Using clsMessage As New Framework.Message("Restarting TCP/IP-server")
                clsMessage.FreezeMessage()
                [Stop](clsMessage)
                Start()
            End Using
        End If
        If Settings.AutoConnect And mReconnectThread Is Nothing Then
            StartAutoConnect()
        ElseIf Not Settings.AutoConnect And mReconnectThread IsNot Nothing Then
            Using clsMessage As New Framework.Message()
                StopAutoConnect(clsMessage)
            End Using
        End If
    End Sub


    Public Shared Sub IssuePairupPinCode(ByVal buddy As Entities.Buddy, ByVal forEmail As Boolean)
        Dim sPinCode As String = CInt(100000000 * Rnd()).ToString("00000000")
        buddy.PairUpPinCode = sPinCode
        buddy.PassCode_LoginAtBuddy = Nothing
        buddy.PassCode_LoginHere = Nothing
        If forEmail Then
            buddy.PairUpValidUntil = Now.AddDays(1).ToUniversalTime
        Else
            buddy.PairUpValidUntil = Now.AddMinutes(5).ToUniversalTime
        End If
    End Sub

    Public Shared Function StartConnectAndPairUp(ByVal host As String, ByVal port As Integer, ByVal buddyNr As Integer, ByVal pinCode As String) As Boolean
        Dim clsNode As New Node()
        Manager.AddNode(clsNode)
        clsNode.StartPairUp(host, port, buddyNr, pinCode)
        Return True
    End Function
    Public Shared Sub StartConnect(ByVal buddy As Entities.Buddy, ByVal isAutomatedRetry As Boolean)
        Select Case GetBuddyStatus(buddy)
            Case BuddyStatus.Offline
                Dim clsNode As New Node
                clsNode.Buddy = buddy
                Manager.AddNode(clsNode)
                clsNode.StartConnect(isAutomatedRetry)
        End Select
    End Sub
    Public Shared Sub Disconnect(ByVal buddy As Entities.Buddy)
        For Each clsNode As Node In GetAllNodes()
            If clsNode.Buddy.Id = buddy.Id Then
                clsNode.Disconnect()
                Exit For
            End If
        Next
    End Sub


    Friend Shared Sub OnNodeException(ByVal node As Node, ByVal exception As Exception)
        Framework.Log.Provider.LogError(String.Format("{0}: Exeption occured: {1}", node.Name, exception.Message), exception)
    End Sub
    Friend Shared Sub OnNodeLogEvent(ByVal node As Node, ByVal text As String)
        Framework.Log.Provider.LogInformation(String.Format("{0}: {1}", node.Name, text))
    End Sub

    Private Shared mNodes As New Generic.List(Of Node)
    Private Shared Function GetAllNodes() As Node()
        SyncLock mNodes
            Return mNodes.ToArray
        End SyncLock
    End Function
    Friend Shared Sub AddNode(ByVal node As Node)
        SyncLock mNodes
            mNodes.Add(node)
        End SyncLock
    End Sub
    Friend Shared Sub RemoveNode(ByVal node As Node)
        SyncLock mNodes
            mNodes.Remove(node)
        End SyncLock
    End Sub

    Public Enum BuddyStatus
        Offline
        PairingUp
        PairUpPending
        Online
        Connecting
        Unknown
        Disconnecting
    End Enum

    Public Shared Function GetAllOnline() As Generic.List(Of Node)
        Dim aRet As New Generic.List(Of Framework.Net.Node)

        For Each clsNode As Node In GetAllNodes()
            If clsNode.IsOnline Then aRet.Add(clsNode)
        Next
        Return aRet
    End Function

    Public Shared Function GetNodeByOneTimeDataChannelPassCode(ByVal passCode As String) As Node
        For Each clsNode As Node In GetAllNodes()
            If clsNode.OneTimeDataChannelPassCode = passCode Then
                Return clsNode
            End If
        Next
        Return Nothing
    End Function

    Public Shared Function IsBuddyOnline(ByVal buddy As Entities.Buddy) As Boolean
        For Each clsNode As Node In GetAllNodes()
            If clsNode.Buddy.Id = buddy.Id Then
                Return clsNode.IsOnline
            End If
        Next
        Return False
    End Function

    Public Shared Function GetOnlineByBuddyId(ByVal buddyId As Integer) As Node
        For Each clsNode As Node In GetAllNodes()
            If clsNode.IsOnline AndAlso clsNode.Buddy.Id = buddyId Then
                Return clsNode
            End If
        Next
        Return Nothing
    End Function

    Private Shared Function GetBuddyStatus(ByVal buddy As Entities.Buddy) As BuddyStatus
        If buddy Is Nothing Then Return BuddyStatus.Connecting
        For Each clsNode As Node In GetAllNodes()
            If clsNode.Buddy IsNot Nothing AndAlso clsNode.Buddy.Id = buddy.Id Then
                If clsNode.IsDisconnecting Then
                    Return BuddyStatus.Disconnecting
                ElseIf clsNode.IsOnline Then
                    Return BuddyStatus.Online
                ElseIf String.IsNullOrEmpty(buddy.PairUpPinCode) Then
                    Return BuddyStatus.Connecting
                Else
                    Return BuddyStatus.PairingUp
                End If
            End If
        Next
        If String.IsNullOrEmpty(buddy.PairUpPinCode) Then
            Return BuddyStatus.Offline
        Else
            Return BuddyStatus.PairUpPending
        End If
    End Function

    Public Shared Function GetBuddyStatusText(ByVal buddy As Entities.Buddy) As String
        Dim udtStatus As BuddyStatus = GetBuddyStatus(buddy)
        Select Case udtStatus
            Case BuddyStatus.Offline
                Return "Offline"
            Case BuddyStatus.Online
                Return "Online"
            Case BuddyStatus.PairingUp
                Return "Paring up..."
            Case BuddyStatus.PairUpPending
                Return "Pair up pending"
            Case BuddyStatus.Connecting
                Return "Connecting..."
            Case BuddyStatus.Unknown
                Return "Unknown"
            Case Else
                Return udtStatus.ToString
        End Select
    End Function

    Private Shared mRequestCommandHandlers As New Generic.Dictionary(Of String, CommandBase)
    Private Shared mReplyCommandHandlers As New Generic.Dictionary(Of String, CommandBase)

    Public Shared Sub RegisterCommand(ByVal command As CommandBase)
        Dim strCode As String = command.RequestCode
        If Not String.IsNullOrEmpty(strCode) Then mRequestCommandHandlers.Add(strCode, command)
        strCode = command.ReplyCode
        If Not String.IsNullOrEmpty(strCode) Then mReplyCommandHandlers.Add(strCode, command)
    End Sub


    'Private Shared mMessageSubscribers As New Generic.Dictionary(Of String, IMessageSubscriber)
    'Public Shared Sub RegisterMessageSubscriber(ByVal handler As IMessageSubscriber)
    '    mMessageSubscribers.Add(handler.Code, handler)
    'End Sub

    Friend Shared Sub OnCommandMessage(ByVal envelope As Framework.Net.MessageEnvelope)
        System.Threading.ThreadPool.QueueUserWorkItem(AddressOf SendMessageToCommandHandler, envelope)
    End Sub
    Private Shared Sub SendMessageToCommandHandler(ByVal param As Object)
        Dim clsEnvelope As MessageEnvelope = CType(param, MessageEnvelope)
        Try
            Dim strCode As String = clsEnvelope.Message.Code

            If mRequestCommandHandlers.ContainsKey(strCode) Then
                mRequestCommandHandlers.Item(strCode).HandleRequest(clsEnvelope)
            ElseIf mReplyCommandHandlers.ContainsKey(strCode) Then
                mReplyCommandHandlers.Item(strCode).HandleReply(clsEnvelope)
            Else
                Manager.OnNodeLogEvent(clsEnvelope.Node, String.Format("Message to command code ""{0}"" was discarded!", strCode))
            End If
        Catch ex As Exception
            Manager.OnNodeException(clsEnvelope.Node, ex)
        End Try
    End Sub


    Private Shared mNextMessageId As Integer = 0
    Private Shared mNextMessageIdLock As New Object
    ''' <summary>
    ''' Returns a new unique MessageId. This method is thread safe!
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetNextMessageId() As Integer
        SyncLock mNextMessageIdLock
            If mNextMessageId = 2 ^ 30 Then mNextMessageId = 0
            mNextMessageId += 1
            Return mNextMessageId
        End SyncLock
    End Function

    Public Shared Sub ViewNetworkPackage(ByVal owner As System.Windows.Forms.IWin32Window, ByVal package As NetworkDataPackage)
        FViewNetworkPackage.Show(owner, package)
    End Sub

    ''' <summary>
    ''' Returns rate in bytes per second
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetCurrentUploadDataRate() As Integer
        Return ThrottledNetworkStream.GetCurrentUploadDataRate
    End Function
    ''' <summary>
    ''' Returns rate in bytes per second
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetCurrentDownloadDataRate() As Integer
        Return ThrottledNetworkStream.GetCurrentDownloadDataRate
    End Function

    Private Shared mDataChannelsByName As New Generic.Dictionary(Of String, IDataChannel)
    Private Shared mDataChannelsByPrio As New Generic.SortedList(Of DataChannelPriority, IDataChannel)

    Public Shared Sub RegisterDataChannel(ByVal channel As IDataChannel)
        mDataChannelsByName.Add(channel.Name, channel)
        mDataChannelsByPrio.Add(channel.Priority, channel)
    End Sub
    Friend Shared Function GetNextDataBlock(ByVal node As Node, ByRef ret_ChannelName As String) As OutgoingDataChannelBlock
        For Each clsChannel As IDataChannel In mDataChannelsByPrio.Values
            Dim clsBlockToSend As OutgoingDataChannelBlock = clsChannel.GetNextBlockToSend(node)
            If clsBlockToSend IsNot Nothing Then
                ret_ChannelName = clsChannel.Name
                Return clsBlockToSend
            End If
        Next
        Return Nothing
    End Function
    Friend Shared Sub BlockSent(ByVal node As Node, ByVal channelName As String, ByVal block As OutgoingDataChannelBlock)
        Dim clsChannel As IDataChannel = mDataChannelsByName(channelName)
        clsChannel.BlockSent(node, block)
    End Sub
    Friend Shared Function GetStreamForIncomingBlock(ByVal node As Framework.Net.Node, ByVal channelName As String, ByVal referenceId As String) As System.IO.Stream
        Dim clsChannel As IDataChannel = mDataChannelsByName(channelName)
        Return clsChannel.GetStreamForIncomingBlock(node, referenceId)
    End Function
    Friend Shared Sub BlockArrived(ByVal channelName As String, ByVal node As Framework.Net.Node, ByVal block As IncomingDataChannelBlock)
        Dim clsChannel As IDataChannel = mDataChannelsByName(channelName)
        clsChannel.BlockArrived(node, block)
    End Sub
End Class

Public Interface IDataChannel
    ReadOnly Property Name() As String
    Function GetNextBlockToSend(ByVal node As Framework.Net.Node) As Framework.Net.OutgoingDataChannelBlock
    Sub BlockSent(ByVal node As Framework.Net.Node, ByVal block As Framework.Net.OutgoingDataChannelBlock)
    ReadOnly Property Priority() As DataChannelPriority
    Sub BlockArrived(ByVal node As Framework.Net.Node, ByVal block As IncomingDataChannelBlock)
    Function GetStreamForIncomingBlock(ByVal node As Framework.Net.Node, ByVal referenceId As String) As System.IO.Stream
End Interface
Public Enum DataChannelPriority As Byte
    Low = 0
    Normal = 5
    High = 10
End Enum